package scheduler;

import java.util.Random;

/**
 * A stub for your first scheduler code
 */
public class Scheduler1 implements Scheduler {

	/**
	 * @see scheduler.Scheduler#authors()
	 */
	public String authors() {
		// TODO Your Code Here!
		return "Lucas Cramer, 70133095 " +
				"Hyunje (Ethan) Jang, 23505092";
	}

	/**
	 * @see scheduler.Scheduler#schedule(scheduler.SchedulingProblem)
	 */
	public ScheduleChoice[] schedule(SchedulingProblem pProblem) {
		// TODO Your Code Here!
		
		Random rand = new Random();
		Room[] rooms = pProblem.getRoomList();
		
		// We begin from a random state (i.e. random assignment of the variables, where a variable is an exam)
		// An assignment is when every course is assigned to an exam slot (date, time, room)
		
		ScheduleChoice[] currentState = randomState(pProblem);
		int randomRestartCounter = 0; // used for random restart of state
		
		while (violatedConstraints(pProblem, currentState) != 0) {
		
			int leastConstraints = Integer.MAX_VALUE;
			
			// Generate neighbours of currentState, keeping track of which has the lowest number of constraints
			
			int bestIndex = 0; // index to change in currentState
			int bestDay = 0; // day to change in currentState
			int bestTime = 0; // time to change in currentState
			
			for (int i=0; i<currentState.length; i++) {
				
				ScheduleChoice[] currentNeighbour = currentState.clone(); // so that we don't change currentState
				ScheduleChoice examToChange = new ScheduleChoice(currentNeighbour[i]); // so that we don't change currentState
				
				Course examCourse = examToChange.getCourse();
				int examDay = examToChange.getDay();
				int examTime = examToChange.getTimeSlot();
				int newRoom = 0;
				Boolean changeRoom = false;
				
				int toChange = rand.nextInt(3); // decides which of day, time, or room to change
				
				switch (toChange) {
				case 0: // change the day by 1
					if (examDay == 4)
						examDay = 0;
					else examDay++;
					break;
				case 1: // change the time by 1
					if (examTime == 3)
						examTime = 0;
					else examTime++;
					break;
				case 2:
					newRoom = rand.nextInt(rooms.length);
					changeRoom = true;
				default:
					break;
				}
				
				if (!changeRoom)
					examToChange.setTime(examDay, examTime); // changes either the day/time of the exam
				else examToChange = new ScheduleChoice(examCourse, rooms[newRoom], examDay, examTime); // changes room of the exam
				
				currentNeighbour[i] = examToChange;
				
				int neighbourConstraints = violatedConstraints(pProblem, currentNeighbour);
				
				// check if currentNeighbour is the best so far
				if (neighbourConstraints <= leastConstraints) {
					leastConstraints = neighbourConstraints;
					bestIndex = i;
					bestDay = examToChange.getDay();
					bestTime = examToChange.getTimeSlot();
				}		
			}
		
			// then move to the best neighbour (the one violating the lowest number of constraints)
			
			currentState[bestIndex].setTime(bestDay, bestTime);
			
			
			// check for random restart
			randomRestartCounter++;
			
			if (randomRestartCounter == 500) {
				currentState = randomState(pProblem);
				randomRestartCounter = 0;
			}	
			
		}
		return currentState;
	}
	
	public int violatedConstraints(SchedulingProblem pInstance, ScheduleChoice[] pSolution) {
		
		int conflicts = 0;
		
		ScheduleChoice[] schedule = pSolution;
		Course[] courseList = pInstance.getCourseList();
		Student[] studentList = pInstance.getStudentList();

		/* Check for incomplete Schedules */
		if (pSolution.length != courseList.length) {
			System.err.println("Incomplete Exam Schedule!");
			return Integer.MAX_VALUE;
		}
		/* Check for exam collisions */
		for (int i = 0; i < courseList.length; i++) {
			if (schedule[i].getDay() >= pInstance.getExamPeriod() || schedule[i].getDay() < 0) {
				System.err.println("Day " + schedule[i].getDay() + "is an impossible day (" + schedule[i].getCourse().getCourseName() + ")");
				return Integer.MAX_VALUE;
			}
			for (int j = i + 1; j < courseList.length; j++) {
				if (schedule[i].getRoom() == schedule[j].getRoom() && schedule[i].getDay() == schedule[j].getDay()
						&& schedule[i].getTimeSlot() == schedule[j].getTimeSlot()) {
					conflicts++;
				}
			}
		}
		/* Check for student conflicts */
		for (int i = 0; i < studentList.length; i++) {
			ScheduleChoice[] local = new ScheduleChoice[Student.scheduleSize];
			/* Get the scheduling choices */
			for (int j = 0; j < Student.scheduleSize; j++) {
				for (int k = 0; k < schedule.length; k++) {
					if (schedule[k].getCourse() == studentList[i].getCourse(j)) {
						local[j] = schedule[k];
					}
				}
			}
			/* Pairwise-compare */
			for (int j = 0; j < Student.scheduleSize; j++) {
				for (int k = j + 1; k < Student.scheduleSize; k++) {
					if (local[j].getTimeSlot() == local[k].getTimeSlot() && local[j].getDay() == local[k].getDay() && j != k) {
						conflicts++;
					}
				}
			}
		}
		return conflicts;
	}
	
	public ScheduleChoice[] randomState(SchedulingProblem pProblem) {
		
		Course[] courses = pProblem.getCourseList();
		Room[] rooms = pProblem.getRoomList();
		Random rand = new Random();
		
		// We begin from a random state (i.e. random assignment of the variables, where a variable is an exam)
		// An assignment is when every course is assigned to an exam slot (date, time, room)
		
		ScheduleChoice[] currentState = new ScheduleChoice[courses.length];
		
		for (int i=0; i < currentState.length; i++) {
			int room = rand.nextInt(rooms.length);
			int day = rand.nextInt(5); // there are 5 possible days
			int time = rand.nextInt(4); // there are 4 possible times
			currentState[i] = new ScheduleChoice(courses[i], rooms[room], day, time);
		}
		
		return currentState;
	}
}
